//******************************************************************************
//
// Copyright (c) 2015 Microsoft Corporation. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//******************************************************************************

#import <Starboard.h>
#import <Foundation/NSURLRequest.h>
#import <Foundation/NSString.h>
#import <Foundation/NSMutableDictionary.h>
#import <NSURLRequestInternal.h>

@interface NSURLRequest ()
@property (readwrite, copy) NSURL* URL;
@property (readwrite) NSURLRequestCachePolicy cachePolicy;
@property (readwrite) NSTimeInterval timeoutInterval;
@property (readwrite, copy) NSURL* mainDocumentURL;
@property (readwrite) NSURLRequestNetworkServiceType networkServiceType;
@property (readwrite) BOOL HTTPShouldUsePipelining;

@property (readwrite, copy) NSString* HTTPMethod;
@property (readwrite, copy) NSData* HTTPBody;
@property (readwrite, retain) NSInputStream* HTTPBodyStream;
@property (readwrite, copy) NSMutableDictionary* allHTTPHeaderFields;
@property (readwrite) BOOL HTTPShouldHandleCookies;
@property (readwrite) BOOL allowsCellularAccess;
@end

@implementation NSURLRequest
/**
 @Status Caveat
 @Notes cachePolicy and timeoutInterval not supported
*/
- (instancetype)initWithURL:(NSURL*)theURL cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeout {
    if (self = [super init]) {
        _URL = [theURL copy];
        _timeoutInterval = timeout;
        _HTTPMethod = @"GET";
        _HTTPShouldHandleCookies = YES;
        _cachePolicy = cachePolicy;
    }
    return self;
}

/**
 @Status Interoperable
*/
- (instancetype)init {
    return [self initWithURL:nil];
}

/**
 @Status Interoperable
*/
- (instancetype)initWithURL:(NSURL*)theURL {
    return [self initWithURL:theURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
}

/**
 @Status Interoperable
*/
+ (instancetype)requestWithURL:(NSURL*)theURL {
    return [[[self alloc] initWithURL:theURL] autorelease];
}

/**
 @Status Caveat
 @Notes cachePolicy and timeoutInterval not supported
*/
+ (instancetype)requestWithURL:(NSURL*)theURL cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(double)timeout {
    return [[[self alloc] initWithURL:theURL cachePolicy:cachePolicy timeoutInterval:timeout] autorelease];
}

- (instancetype)_initWithURLRequest:(NSURLRequest*)other {
    if (self = [super init]) {
        self.URL = other->_URL;
        self.cachePolicy = other->_cachePolicy;
        self.timeoutInterval = other->_timeoutInterval;
        self.HTTPMethod = other->_HTTPMethod;
        self.HTTPBody = other->_HTTPBody;
        self.HTTPShouldHandleCookies = other->_HTTPShouldHandleCookies;
        self.HTTPBodyStream = other->_HTTPBodyStream;
        self.mainDocumentURL = other->_mainDocumentURL;
        self.networkServiceType = other->_networkServiceType;
        self.HTTPShouldUsePipelining = other->_HTTPShouldUsePipelining;
        self.allowsCellularAccess = other->_allowsCellularAccess;

        self->_properties.attach([other->_properties mutableCopy]);

        // Setter bypass: the autogenerated setter will take an *immutable* copy.
        _allHTTPHeaderFields = [other->_allHTTPHeaderFields mutableCopy];
    }
    return self;
}

/**
 @Status Interoperable
*/
- (instancetype)mutableCopyWithZone:(NSZone*)zone {
    return [[NSMutableURLRequest alloc] _initWithURLRequest:self];
}

/**
 @Status Interoperable
*/
- (instancetype)copyWithZone:(NSZone*)zone {
    return [self retain];
}

/**
 @Status Interoperable
*/
+ (BOOL)supportsSecureCoding {
    return YES;
}

/**
 @Status Interoperable
*/
- (void)encodeWithCoder:(NSCoder*)coder {
    [coder encodeObject:_URL forKey:@"URL"];
    [coder encodeObject:_mainDocumentURL forKey:@"mainDocumentURL"];
    [coder encodeObject:_HTTPMethod forKey:@"HTTPMethod"];
    [coder encodeObject:_HTTPBody forKey:@"HTTPBody"];
    //[coder encodeObject:_HTTPBodyStream forKey:@"HTTPBodyStream"];
    [coder encodeObject:_allHTTPHeaderFields forKey:@"allHTTPHeaderFields"];
    [coder encodeInt64:_cachePolicy forKey:@"cachePolicy"];
    [coder encodeDouble:_timeoutInterval forKey:@"timeoutInterval"];
    [coder encodeInt64:_networkServiceType forKey:@"networkServiceType"];
    [coder encodeBool:_HTTPShouldUsePipelining forKey:@"HTTPShouldUsePipelining"];
    [coder encodeBool:_HTTPShouldHandleCookies forKey:@"HTTPShouldHandleCookies"];
    [coder encodeBool:_allowsCellularAccess forKey:@"allowsCellularAccess"];

    [coder encodeObject:_properties forKey:@"_properties"];
}

/**
 @Status Interoperable
*/
- (id)initWithCoder:(NSCoder*)coder {
    if (self = [super init]) {
        _URL = [[coder decodeObjectOfClass:[NSURL class] forKey:@"URL"] retain];
        _mainDocumentURL = [[coder decodeObjectOfClass:[NSURL class] forKey:@"mainDocumentURL"] retain];
        _HTTPMethod = [[coder decodeObjectOfClass:[NSString class] forKey:@"HTTPMethod"] retain];
        _HTTPBody = [[coder decodeObjectOfClass:[NSData class] forKey:@"HTTPBody"] retain];
        //_HTTPBodyStream = [[coder decodeObjectOfClass:[NSInputStream class] forKey:@"HTTPBodyStream"] retain];
        _allHTTPHeaderFields = [[coder decodeObjectOfClass:[NSDictionary class] forKey:@"allHTTPHeaderFields"] retain];
        _cachePolicy = (NSURLRequestCachePolicy)[coder decodeInt64ForKey:@"cachePolicy"];
        _timeoutInterval = [coder decodeDoubleForKey:@"timeoutInterval"];
        _networkServiceType = (NSURLRequestNetworkServiceType)[coder decodeInt64ForKey:@"networkServiceType"];
        _HTTPShouldUsePipelining = [coder decodeBoolForKey:@"HTTPShouldUsePipelining"];
        _HTTPShouldHandleCookies = [coder decodeBoolForKey:@"HTTPShouldHandleCookies"];
        _allowsCellularAccess = [coder decodeBoolForKey:@"allowsCellularAccess"];

        _properties = [coder decodeObjectForKey:@"_properties"];
    }
    return self;
}

/**
 @Status Interoperable
*/
- (BOOL)supportsSecureCoding {
    return YES;
}

/**
 @Status Interoperable
*/
- (BOOL)isEqual:(id)other {
    if (self == other) {
        return YES;
    }

    if (![other isKindOfClass:[NSURLRequest class]]) {
        return NO;
    }

    NSURLRequest* otherRequest = static_cast<NSURLRequest*>(other);

    return
        [_URL isEqual:otherRequest->_URL] && //
        ((_HTTPMethod == otherRequest->_HTTPMethod) || [_HTTPMethod isEqual:otherRequest->_HTTPMethod]) && //
        ((_HTTPBody == otherRequest->_HTTPBody) || [_HTTPBody isEqual:otherRequest->_HTTPBody]) && //
        ((_HTTPBodyStream == otherRequest->_HTTPBodyStream) || [_HTTPBodyStream isEqual:otherRequest->_HTTPBodyStream]) && //
        ((_mainDocumentURL == otherRequest->_mainDocumentURL) || [_mainDocumentURL isEqual:otherRequest->_mainDocumentURL]) && //
        (_cachePolicy == otherRequest->_cachePolicy) && //
        (_timeoutInterval == otherRequest->_timeoutInterval) && //
        (_HTTPShouldHandleCookies == otherRequest->_HTTPShouldHandleCookies) && //
        (_networkServiceType == otherRequest->_networkServiceType) && //
        (_HTTPShouldUsePipelining == otherRequest->_HTTPShouldUsePipelining) && //
        (_allowsCellularAccess == otherRequest->_allowsCellularAccess) && //
        ((_properties.get() == otherRequest->_properties.get()) || [_properties isEqual:otherRequest->_properties]) && //
        ((_allHTTPHeaderFields == otherRequest->_allHTTPHeaderFields) || [_allHTTPHeaderFields isEqual:otherRequest->_allHTTPHeaderFields]);
}

/**
 @Status Interoperable
*/
- (unsigned int)hash {
    return [self.URL hash];
}

/**
 @Status Caveat
 @Notes Only limited header fields are supported.
*/
- (NSString*)valueForHTTPHeaderField:(NSString*)field {
    id ret = [_allHTTPHeaderFields objectForKey:field];
    const char* pName = [field UTF8String];

    if (strcmp(pName, "Accept-Language") == 0) {
        return @"en-us";
    } else if (strcmp(pName, "X-HTTP-Method-Override") == 0) {
        return nil;
    } else {
        if (ret) {
            return ret;
        }

        if (strcmp(pName, "User-Agent") == 0) {
            return nil;
        }

        UNIMPLEMENTED_WITH_MSG("%s HTTP header field is not supported in valueForHTTPHeaderField", pName);
    }

    return nil;
}

/**
 @Status Interoperable
*/
- (void)dealloc {
    [_HTTPBodyStream release];
    [_URL release];
    [_mainDocumentURL release];
    [_HTTPMethod release];
    [_HTTPBody release];
    [_allHTTPHeaderFields release];
    [super dealloc];
}

- (void)_lazyInitProperties {
    @synchronized(self) {
        if (!_properties) {
            _properties.attach([NSMutableDictionary<NSString*, id> new]);
        }
    }
}

- (id)_propertyForKey:(NSString*)key {
    @synchronized(self) {
        return [_properties objectForKey:key];
    }
}

- (void)_setProperty:(id)value forKey:(NSString*)key {
    @synchronized(self) {
        [self _lazyInitProperties];

        if (value) {
            [_properties setObject:value forKey:key];
        } else {
            [_properties removeObjectForKey:key];
        }
    }
}

- (void)_removePropertyForKey:(NSString*)key {
    @synchronized(self) {
        [self _lazyInitProperties];
        [_properties removeObjectForKey:key];
    }
}

@end
